package cn.blank.calc;

/**
 * 位运算符
 * <br/><a href="https://segmentfault.com/a/1190000023918713">谈谈二进制（三）——位运算及其应用</a>
 *
 * @author _blank    DateTime 2022/7/18 12:25:00
 */
public class BitwiseOperators {
    /*
     * 位逻辑运算符
     *     在很多系统程序中常要求在位（bit）一级运算或处理，位运算原本属于汇编语言的功能，由于C语言是介于高级语言和汇编语言之间的一种中间语言，是为开发系统软件而设计的，所以它提供了多种类似于汇编语言的功能。
     *     C语言提供了位运算的功能，这使得C语言也能像汇编语言一样用来编写系统程序。
     *     位运算是C语言的一种特殊运算能力，它是以二进制位为单位进行运算的，即进行数的二进制位的运算。
     *     例如，一个字符型数据占据8个二进制位（1个字节），则最右边的二进制位称为第0位，最左边的二进制位称为最高位。
     *     位运算符分为位逻辑运算符、位移位运算符和位自反赋值运算符三种。
     *     位运算对象只能是整型（int、short、unsigned、long）或字符型（char）数据。
     *     1、位逻辑运算符
     *         位逻辑运算符包括按位非、按位与、按位或和按位异或。具体如表5-8和表5-9所示。
     *         表5-8    位逻辑运算符
     *         |----运算符----------名称-------------应用举例---------------功能说明-----------------|
     *         |------ ~ ------|----按位非-------|---- ~a  ---------|----将a按位取反----------------|
     *         |------ & ------|----按位与-------|---- a&b ---------|----将a和b按位相与-------------|
     *         |------ | ------|----按位或-------|---- a|b ---------|----将a和b按位相或-------------|
     *         |------ ^ ------|----按位异或-----|---- a^b ---------|----将a和b按位相异或------------|
     *             表5-9    位逻辑运算符的运算规则
     *         | ---- 对象a ---- 对象b ---- a&b ---- a|b ---- a^b ---- ~a ---- |
     *         | ------- 0  ---- 0 -------- 0 ----   0 ----   0 ------ 1 ---- |
     *         | ------- 0  ---- 1 -------- 0 ----   1 ----   1 ------ 1 ---- |
     *         | ------- 1  ---- 0 -------- 0 ----   1 ----   1 ------ 0 ---- |
     *         | ------- 1  ---- 1 -------- 1 ----   1 ----   0 ------ 0 ---- |
     *         根据表5-8、表5-9对位逻辑运算符做以下几点说明：
     *         （1）按位非（~）运算符是单目运算符，它的运算对象只有一个，它的结合性是自右向左。
     *             其功能是对参与运算的数的各二进位按位求反。例如：~0=1，~1=0。
     *             例如：二进制代码 00000101 按位求反，结果为-6 ()
     *             （执行过程是先将5的二进制码取反运算包括符号位，然后在取反转换成负数的原码在末位的二进制数进一）
     *             （正数：原码 = 反码 = 补码）
     *             （负数：反码 为原码的最高位不变，其余位取反；补码 在反码的基础上加一）
     *         （2）按位与运算符（&）是双目运算符，它的结合性是自左向右。
     *             其功能是参与运算的两数的二进制各对应位相与。
     *             只有对应的两个二进制位均为1时，结果位才为1，否则为0。
     *             例如：二进制代码 00001001 和 00000101 相与，结果为1 (00000001)₂。
     *             （按位与运算就是将9的二进制上的每个bit位与5的二进制上的每个bit位比较，如果两个bit位上的数相同则保留否则为0！）
     *             按位与运算通常用来对某些位清0.
     *             根据对应位同为1时才为1的原则，为了使某数的指定位清零，可将数按位与一特定数相与，该数中和为1的位，特定数中对应的位为0；
     *             该数中的为0的位，特定数中对应的位为0或1均可。
     *             按位与运算还可以用来取一数中的某些位，对要取的那些位，特定数中相应的位设为1即可。
     *         （3）按位或运算符（|）是双目运算符，它的结合性是自左向右。
     *             其功能是参与运算的两数的二进制各对应位相或。
     *             只要对应的两个二进位有一个为1时，结果位就为1.
     *             参与运算的两个数均以补码出现。
     *             例如：二进制代码 00001001 和 00000101 相或，结果为13 (00001101)₂。
     *             （如果bit位上的数为1则保留否则为0）
     *             按位或运算可将数据的某些位置1，将与该数按位或的特定数的相应位置1即可。
     *         （4）按位异或运算符（^）是双目运算符，它的结合性是自左向右。
     *             其功能是参与运算的两数的二进制各对应位相异或，当两对应的二进位上的数不相同时，结果为1.
     *             例如：二进制代码 00001001 和 00000101 相异或，结果为12 (00001100)₂。
     *             (如果两个bit位上的数不同则保留为1相同则为0)
     *             按位异或运算是当两个数的对应位不同时，结果为1，所以按位异或运算可以把数的某些位反转，即0变1,1变0，保留某些位。
     *     2、位逻辑运算符的优先级
     *         在位逻辑运算符中优先级如图5-8所示。
     *         与前面已经学习过过的其他运算符相比，位逻辑运算符的优先级如图5-98所示。
     *         ~    &    ^    |                   !    ~    算术运算符    关系运算符    &    ^    |    &&    ||
     *         ---------------->                 ------------------------------------------------------------>
     *         高             低                  高                                                         低
     *         图5-8 位逻辑运算符                                      图5-9 位逻辑运算符
     * 移位运算符
     *     移位运算是将数据看成二进制数，对其进行向左或右移动若干位的运算。
     *     移位运算包括左移位运算和右移位运算。
     *     1、移位运算符
     *         移位运算符包括左移位运算符和右移位运算符，都是双目运算符，有两个运算对象。
     *         第一个运算对象是要移位的运算对象，第二个运算对象时所移的二进制位数。
     *         移位运算符的表示形式、举例和功能如表5-10表示。
     *                                     图5-10 移位运算符
     *         --------运算符----------名称------------应用举例----------功能说明---------
     *         -------- << --------左移动运算符-------- a<<b -------- 将a向左移b位--------
     *         -------- >> --------右移动运算符-------- a>>b -------- 将a向右移b位--------
     *         对于移位运算符做以下几点说明：
     *         （1）左移运算符是将一个数的二进位全部左移若干位。“<<”左边的数是要移位的数，右边的数指定移动的位数。
     *              左移的规则是将二进制数向左移动若干位，左边移走的高位被丢弃，右边被空出来的低位补零。
     *              移位运算符的操作对象为整型，有无符号数和带符号数之分，但对于左移运算来说，若高位左移后溢出，则舍弃，低位补0.
     *              例如：a=a<<2，则a的二进制数左移2位，右补0。
     *              若a=13，即二进制数00001101，则最后a=52。（a=00001101=13  =>  a<<1=00011010=26  =>  a<<2=00110100=52）
     *         （2）右移运算符是将一个数的二进位全部右移若干位。“>>”左边的数是要移位的数，右边的数指定移动的位数。
     *              右移的规则是将二进制数向右移动若干位，右移与被移位的数据是否带符号有关。
     *              对于无符号整数来讲，左端空出的高位全部补0。
     *              而对于有符号的数来讲，如果符号位为0（即正数），则被空出来的高位部分补零。
     *              如果符号为为1（即负数），则被空出来的高位部分补0还是补1，与使用的计算机系统有关，有的计算机系统补0，有的计算机系统补1。
     *              例如：a=a>>2，将a的二进制数右移2位，左补0。
     *              若a=13，即二进制数00001101，则最后a=3。（a=00001101=13  =>  a>>1=00000110=6  =>  a>>2=00000011=3）
     *              若a=-15，即计算机内的二进制表示为11110001，则最后a=-4。（a=11110001=-15  =>  a>>1=11111000  =>  a>>2=11111100=-4）
     *              （11111100是-4在计算机内以其二进制补码的表示形式（这个结果是使用visual c++ 6.0环境运行的结果））。
     *     2、移位运算的优先级
     *         如果在一个式子中出现多种运算符，要按照运算符的优先级顺序进行运算，移位运算符在位运算符中的优先顺序如图5-10所示。
     *         移位运算符与前面学习过的几种运算符混合使用时的运算优先顺序如图5-11所示。
     *         ~    <<(>>)    &    ^    |              算术运算符        移位运算符        关系运算符
     *         -------------------------->             ------------------------------------------>
     *         高                       低              高                                      低
     *         图5-10 移位运算符的优先级                       图5-11 移位运算符的优先级
     * 位自反赋值运算符
     *     在前面学习赋值运算符与其表达式的时候，我们已经提到过位自反赋值运算符。
     *     复合赋值运算符是由某个规定的运算符和基本赋值运算符组合而成的，当组成复合赋值运算符中的“某个规定的运算符”是位运算符时，复合赋值运算符就称为位自反赋值运算符。
     *     具体如表5-11所示。
     *                              表5-11 位自反赋值运算符
     *     -----赋值运算符------------名称------------------------应用举例-----------------------------实现功能---------
     *     -------- &= -------------位与赋值---------------- a&=b 等价于 a=a&b ----------------将a与b按位与后赋值给a-----
     *     -------- |= -------------位或赋值---------------- a|=b 等价于 a=a|b ----------------将a与b按位或后赋值给a------
     *     -------- ^= -------------按位异或赋值------------- a^=b 等价于 a=a^b ----------------将a与b按位异或后赋值给a----
     *     -------- <<= ------------位左移赋值--------------- a<<=2 等价于 a=a<<2 --------------将a左移2位后赋值给a-------
     *     -------- >>= ------------位右移赋值--------------- a>>=2 等价于 a=a>>2 --------------将a右移2位后赋值给a-------
     *     位自反赋值运算符与其他赋值运算符的优先级相同，结合性也是自右向左。
     *     位自反赋值运算符的运算规则与算术自反运算符的运算规则相同，详细介绍请参考算术自反运算符的内容。
     */

    private static void example01() {
        System.out.println("--------按位非");
        System.out.println(~0b00000101);
        // 我们平时学习的二进制表示有三种形式:原码，反码和补码；
        // 然而计算机只认识补码，原码和反码只是人们方便计算人为定义的,所以计算机中的所有计算二进制的输入输出都是补码的形式
        // 万能计算公式：~x = -(x + 1)，例如 ~100 = -(100+1) = -101
        System.out.println("原理: (补码 00000101)"// 为了方便，我们这里用一个字节的整数表示(5用二进制表示0101，一个字节表示00000101)
                + " => (~ 按位取反 [补码 11111010])"
                + " => (需要转成人类可理解的二进制)"
                + " => (补码 转 反码 得到 11111001)"// 负数情况下：（补码=反码+1，反之，反码=补码-1）
                + " => (反码 转 原码 得到 11110110)"// 负数情况下：（反码=原码的最高位不变，其余位取反，原码同理）
                + "故：0b0110 为：-" + Integer.toHexString(0b0110));
        System.out.println(~5);

        System.out.println("--------按位与");
        System.out.println(0b00001001 & 0b00000101);
        System.out.println(Integer.toHexString(0b00000001));
        System.out.println(9 & 5);

        System.out.println("--------按位或");
        System.out.println(0b00001001 | 0b00000101);
        System.out.println(Integer.toUnsignedString(0b00001101, 10));
        System.out.println(9 | 5);

        System.out.println("--------按位异或");
        System.out.println(0b00001001 ^ 0b00000101);
        System.out.println(Integer.toUnsignedString(0b00001100, 10));
        System.out.println(9 ^ 5);
    }

    /**
     * 例题分析<br/>
     * 我们知道，数据在计算机中是以二进制补码的形式存储的。<br/>
     * 对于一个正数，原码是将该数转换成二进制，它的反码和补码与原码相同。<br/>
     * 对于一个负数，原码是将该数按照绝对值大小转换成的二进制数，最高位即符号为1；<br/>
     * 它的反码是除符号位外将二进制数按位取反，所得的新二进制数称为原二进制数的反码；<br/>
     * 它的补码是将其二进制的反码加1。<br/>
     * 10的二进制补码为 00001010，12的二进制补码为 00001100（这里为了方便表示，用八位二进制来表示，实际的运算过程中，在visual c++ 6.0环境下，整数值占4个字节，即用32位二进制表示）。<br/>
     * ~i表示对二进制00001010进行按位取反运算，即0变1,1变0，所以~00001010 = 11110101,11110101是一个整型数的补码形式，转换成十进制为-11。<br/>
     * i&j表示00001010与00001100进行按位相与，结果为00001000，最高位为0，表示是一个正数，原码、反码和补码相同，将其转换成二进制为：2³=8。<br/>
     * i|j表示00001010与00001100进行按位相或，结果为00001110，最高位为0，表示是一个正数，原码、反码和补码相同，将其转换成二进制为：2³+2²+2¹=14。<br/>
     * i^j表示00001010与00001100相异或，结果为00000110，最高位为0，表示是一个正数，原码、反码和补码相同，将其转换成二进制为：2²+2¹=6。<br/>
     */
    private static void example02() {
        System.out.println("位逻辑运算应用举例。");
        final int i = 10, j = 12;// 00001010    00001100
        System.out.println("~i = " + ~i);// => 11110101 => 11110100 => 11111011 => -11
        System.out.println("i&j = " + (i & j));// 00001000 => 8
        System.out.println(i | j);// 00001110 => 14
        System.out.println(i ^ j);// 00000110 => 6
    }

    public static void main(String[] args) {
        example01();
        example02();
    }
}
